Skip to content

databricks: support Lakehouse//RT, auto-detecting the SEA backend - #9879

Merged
k-anshul merged 6 commits into
rilldata:mainfrom
dey-abhishek:databricks-rt-sea-main
Sep 17, 2026
Merged

k-anshul merged 6 commits into
rilldata:mainfrom
dey-abhishek:databricks-rt-sea-main

Conversation

@dey-abhishek

@dey-abhishek dey-abhishek commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

databricks: support Lakehouse//RT, auto-detecting the SEA backend (backward compatible with DBSQL)

Problem

Lakehouse//RT (real-time) Databricks warehouses only speak the Statement Execution
API (SEA)
protocol and reject the Thrift/HiveServer2 protocol that databricks-sql-go
uses by default:

BAD_REQUEST: Lakehouse/RT is not supported for Thrift protocol.
Please update your Databricks SQL Driver version to the latest version,
which supports the Statement Execution API protocol  (HTTP 400)

Rill currently pins databricks-sql-go v1.10.0, which predates the driver's SEA/kernel
backend, so Rill cannot connect to Lakehouse//RT warehouses at all — the connector fails
at open, before any query runs.

What this changes

  1. Bump databricks-sql-go v1.10.0 → v1.15.1. v1.15.0 introduced the opt-in
    SEA/kernel backend (useKernel=true / WithUseKernel(true)). The default remains
    Thrift, so this bump alone changes no behavior.
  2. Auto-detect Lakehouse//RT (no configuration needed). On first connect the
    connector probes once; if the warehouse rejects Thrift (the RT signal), it
    transparently switches to the SEA backend. The probe result is cached and shared by
    both the OLAP path and the warehouse ingest path (effectiveDSN). DBSQL warehouses
    accept Thrift and are left unchanged. This means a user can add an RT warehouse in
    the UI with no special knowledge — no use_kernel, no UseThriftClient — and it
    just works.
  3. use_kernel connector property (default false) as an explicit override. Forces
    SEA and skips the probe. Rarely needed given auto-detection, but useful to force SEA.
  4. warehouse.go ingest fallback. The bulk-ingest path prefers
    GetArrowIPCStreams (unchanged Thrift path). The SEA backend doesn't implement IPC
    streams — it exports Arrow C Data — and returns an error wrapping
    dbsqlerr.ErrNotSupportedByKernel. On that sentinel we fall back to
    GetArrowBatches and re-serialize each record to a self-contained Arrow IPC stream
    (via the driver's Arrow v12 ipc.Writer), which the existing v18 ipc.Reader
    parquet path consumes untouched. This bridges the driver's Arrow v12 ↔ Rill's Arrow
    v18 without changing the DBSQL path. Rill's OLAP path (olap.go, sqlx rows) was
    already SEA-compatible and is unchanged.
  5. information_schema.Lookup de-JOINed. The per-table schema lookup JOINed
    information_schema.tables and columns; that join forces a shuffle that RT's Photon
    rejects (PHOTON_INTERNAL_ERROR, retry unsupported), breaking the schema browser.
    Split into two filtered point-lookups (no shuffle) — verified against a real RT
    warehouse (the JOIN fails, both point-lookups succeed); equivalent on DBSQL.

Backward compatibility

  • Default builds (no databricks_kernel tag) compile unchanged; use_kernel defaults to
    false, so DBSQL warehouses are byte-for-byte on the same Thrift path.
  • If use_kernel: true is set in a build without the kernel backend linked, the driver
    fails at connect with a clear error wrapping ErrKernelNotCompiled (no silent Thrift
    fallback, no crash).

Build / distribution note (needs a maintainer decision)

The SEA backend links a native library and is gated behind -tags databricks_kernel
with CGO. Rill already builds with CGO enabled (DuckDB, confluent-kafka), so the only
additional cost is adding the tag and linking the per-platform kernel archive
(databricks-sql-kernel-bindings, ~60–95 MB/platform). This PR makes RT support
available and correct when built with the tag, and a graceful error otherwise. How
(and whether) to enable the tag in shipped release binaries is left as a follow-up for
maintainers.

It was decided to link the library statically. The overall size increase was about 20-25MB.

Testing

  • New unit tests runtime/drivers/databricks/dsn_internal_test.go: TestResolveDSN
    (backend selection — no useKernel by default, set when opted in, DSN pass-through,
    ?/& joining, no duplicate), TestRTRequiresSEA (only the Thrift-not-supported
    message triggers the switch — 403/refused/nil don't), and TestWithUseKernel.
  • Auto-detection verified live against a real Lakehouse//RT warehouse with no
    use_kernel set: the connector logged the Thrift→SEA switch and both the connector
    and a model reconciled; a regular DBSQL warehouse stayed on Thrift and reconciled.
  • Manual, both paths, end to end (kernel-tag build, macOS arm64):
    • Lakehouse//RT (use_kernel: true): connector + a SELECT current_catalog(), now()
      model reconcile successfully and materialize into DuckDB.
    • Regular DBSQL serverless warehouse (use_kernel unset, Thrift): a
      SELECT current_catalog(), current_version(), now() model reconciles successfully —
      current_version() works here but is UNRESOLVED_ROUTINE on RT, confirming the
      Thrift path is unaffected.

Checklist:

  • Covered by tests (unit test for DSN resolution; manual e2e for both backends)
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases (missing kernel build → clear error; no duplicate param)
  • Linked the issues it closes
  • Checked if the docs need to be updated (updated the Databricks connector doc)
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

Lakehouse//RT warehouses only speak the Statement Execution API (SEA) and
reject the Thrift protocol, so Rill (pinned to databricks-sql-go v1.10.0)
could not connect to them at all.

Bump databricks-sql-go v1.10.0 -> v1.15.1 (adds the SEA/kernel backend) and
auto-detect RT: on first connect the connector probes once and, if the
warehouse rejects Thrift, transparently switches to the SEA backend. The
decision is cached and shared by both the OLAP path and the warehouse ingest
path (effectiveDSN), so an RT warehouse works with no configuration. DBSQL
warehouses accept Thrift and are unchanged. A `use_kernel` connector property
(default false) is available as an explicit override.

The SEA backend exports Arrow C Data rather than IPC streams, so the bulk
ingest path in warehouse.go falls back from GetArrowIPCStreams to
GetArrowBatches (re-serialized to a self-contained Arrow IPC stream via the
driver's Arrow v12 writer, which the existing v18 ipc.Reader consumes) when
the driver returns ErrNotSupportedByKernel. The OLAP path already worked over
SEA.

information_schema.Lookup previously JOINed information_schema.tables and
columns; that join forces a shuffle that RT's Photon rejects
(PHOTON_INTERNAL_ERROR, retry unsupported), breaking the schema browser. Split
it into two filtered point-lookups (no shuffle); equivalent on DBSQL.

Adds unit tests for DSN resolution, the RT-detection predicate, and the
useKernel DSN helper, and documents the behavior.

Co-authored-by: Isaac <no-reply@databricks.com>
@k-anshul k-anshul self-assigned this Sep 11, 2026
dey-abhishek and others added 5 commits September 13, 2026 10:35
Adds TestOLAP_LakehouseRT, a live integration test that drives the full Rill
Databricks OLAP path against a Lakehouse//RT (Reyden) SQL warehouse over the
auto-detected SEA backend. The connector config passes only the DSN (no
use_kernel), so a passing query proves the Thrift->SEA autodetection end to end.

Because Lakehouse//RT speaks only SEA, the test requires the SEA-via-kernel
backend and carries //go:build databricks_kernel (CGO), excluding it from the
default Thrift-only build and the standard `go test -short ./...` CI job. It is
otherwise gated like the existing Databricks/Snowflake live tests: a leading
t.Skip disables it by default, testmode.Expensive keeps it out of normal runs,
and it needs RILL_RUNTIME_DATABRICKS_RT_TEST_DSN to point at an RT warehouse.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@nishantmonu51 nishantmonu51 added Type:Feature New feature request Area:Connectors Size:M Medium change: 100-499 lines labels Sep 16, 2026
@k-anshul

Copy link
Copy Markdown
Member

Hey @dey-abhishek

Thanks for the PR.

Statically linking the platform specific binary is not great. Ideally it would have much better if the Go driver like the JDBC and Python driver implemented a pure Go based SEA backend instead of relying on Rust bindings. For now we will link the kernel bindings statically. I made the required changes and simplified some logic as well.

Also I don't have access to Lakehouse RT so just to confirm were you able to do any live testing with the new backend and did you run the tests in file runtime/drivers/databricks/olap_rt_test.go.

@dey-abhishek

Copy link
Copy Markdown
Contributor Author

Thanks @k-anshul! Confirming live testing on this branch, including your static-bindings and simplification changes:

End-to-end via the Rill UI — built with -tags databricks_kernel and pointed a Databricks connector at a Lakehouse//RT warehouse with no use_kernel set. On first connect the driver detected the Thrift rejection and transparently switched to the SEA backend, the schema browser worked, and a materialized model ingested cleanly into DuckDB (row counts verified in the UI). So the Thrift→SEA autodetection, the information_schema lookup split, and the warehouse.go Arrow-batch ingest path all work against RT with zero configuration.

olap_rt_test.goTestOLAP_LakehouseRT passes live against an RT warehouse (DSN-only config, no use_kernel, so it exercises the autodetection): scalar values, session routines (current_catalog / current_schema / current_user), query-schema, and dry-run subtests are all green.

DBSQL (Thrift) behavior is unchanged and was re-verified as well.

@k-anshul

Copy link
Copy Markdown
Member

Thanks for confirming @dey-abhishek

@nishantmonu51 nishantmonu51 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. A transient probe failure permanently poisons the connectorruntime/drivers/databricks/databricks.go, backendDSN

c.dbErr is sticky (getDB short-circuits on it, and backendDSN returns it on entry). Previously it could only be set by sqlx.Open failing, which is deterministic. Now a live PingContext sets it. Databricks warehouses auto-stop, so the first connect after idle starts the warehouse and can time out or return a transient 5xx — after which every subsequent query returns the stale error until the instance is reopened. The ctx.Err() == nil guard does not cover server-side timeouts or network errors. Cache the RT/no-RT decision, not the failure.

2. Every DBSQL connection now pays an extra connect round-trip — same function

backendDSN opens a second pool and pings it before the real pool, for everyone who has not set use_kernel; Ping() then pings again through getDB. On a cold warehouse this doubles the cold-start path. Probing lazily — retry once on a Thrift rejection at first query, reusing rtRequiresSEA — gets the same auto-detection at zero cost for DBSQL.

3. -tags databricks_kernel is enabled unconditionally, which the description says is a follow-upMakefile:7, Makefile:11, .github/workflows/cli-release.yml:73, .github/workflows/rill-cloud.yml:99-101

The body says enabling the tag in shipped binaries is "left as a follow-up for maintainers", but the diff enables it for the released CLI, the cloud image, and local make cli / make cli-only. By the PR's own numbers that is ~60–95 MB of native archive per platform added to the CLI download and to every local dev build. Either drop these four hunks and keep the graceful-error path, or raise the size decision explicitly.

4. olap_rt_test.go:23 is dead code

The unconditional t.Skip makes the whole function unreachable, including via the invocation documented directly above it. The build tag and the RILL_RUNTIME_DATABRICKS_RT_TEST_DSN check already gate it; remove the t.Skip.

5. Lookup now returns ErrNotFound for a missing tableruntime/drivers/databricks/information_schema.go:161

Previously a missing table produced an OlapTable with an empty schema. ErrNotFound looks like the right contract, but it is a behavior change unrelated to the RT fix and is not mentioned in the description. Worth confirming callers handle it (AllFromInformationSchema, schema browser).

@k-anshul

k-anshul commented Sep 17, 2026

Copy link
Copy Markdown
Member

1. A transient probe failure permanently poisons the connectorruntime/drivers/databricks/databricks.go, backendDSN

Yes this is expected. This is what happens in normal connector reconcile as well and the user is expected to refresh the connector.

2. Every DBSQL connection now pays an extra connect round-trip — same function

This is acceptable trade off. We already do a ping and pings are expected to fast.

On a cold warehouse this doubles the cold-start path.

No it does not. The first ping will wake up the warehouse which should make second ping fast. If the warehouse does not wake up then it won't try second ping.

Probing lazily — retry once on a Thrift rejection at first query, reusing rtRequiresSEA — gets the same auto-detection at zero cost for DBSQL.

This requires meaningful code changes and is not very clean.
A similar approach is already being followed in Clickhouse where we do a Ping to determine the protocol and fallback to native if Ping fails.

If we don't want to pay the cost then I think better alternative is to just let the user forcefully select it but it will remove the nice auto detection functionality.

3. -tags databricks_kernel is enabled unconditionally, which the description says is a follow-upMakefile:7, Makefile:11, .github/workflows/cli-release.yml:73, .github/workflows/rill-cloud.yml:99-101

A decision was made to ship the SDK statically.

The new binary is ~10 MB larger than v0.89.4 across all four platforms (~6–8% growth). (zipped size)
Consistent ~26–29 MB (~8%) growth in the actual executable across all platforms

4. olap_rt_test.go:23 is dead code

This is expected. We neither have a permanently running databricks test cluster nor databricks RT cluster so it is expected to spin a cluster when making changes and test accordingly.

5. Lookup now returns ErrNotFound for a missing tableruntime/drivers/databricks/information_schema.go:161

This is an expected contract and the earlier change was a bug.

@nishantmonu51
nishantmonu51 self-requested a review September 17, 2026 08:34
@k-anshul
k-anshul merged commit 892fc2b into rilldata:main Sep 17, 2026
31 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area:Connectors Size:M Medium change: 100-499 lines Type:Feature New feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants